package com.iboxpay.open.auth.center.utils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.google.common.io.Closeables;
import com.iboxpay.base.common.utils.serialize.ProtostuffUtils;
import com.iboxpay.open.auth.center.redis.factory.JedisPoolFactory;

import io.codis.jodis.JedisResourcePool;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
import redis.clients.jedis.exceptions.JedisConnectionException;

/**
 * 
 * JedisUtils.java
 * @author xuxiao
 * @date 2018/06/25
 */
@Service
public class JedisUtils {

    protected static final Logger LOGGER = LoggerFactory.getLogger(JedisUtils.class);

    protected JedisResourcePool jedisPool;
    
    @Autowired
    private JedisPoolFactory jedisPoolFactory;

    @PostConstruct
    public void init(){
    	jedisPool = jedisPoolFactory.createJedisPool();
    }
    
    /**
     * 设置基本类型或集合类型，protostuff默认不支持基本类型和集合类型
     * @param key
     * @param value 泛型，可直接传入任意类型
     */
    public <T> void setExt(String key, T value) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            if (jedis != null) {
                jedis.set(key.getBytes(), ProtostuffUtils.serializer(value, true));
            }
        } catch (JedisConnectionException e) {
            LOGGER.error("can't connect to redis server when set key=" + key, e);
        } catch (Exception e) {
            LOGGER.error("set key error! key=" + key, e);
        } finally {
            try {
                Closeables.close(jedis, true);
            } catch (IOException e) {
                LOGGER.error("close jedis error when set key! key=" + key, e);
            }
        }
    }
    
    /**
     * 获取基本类型或集合类型，protostuff默认不支持基本类型和集合类型
     * @param key
     * @return 返回序列化时传入的类型，无需强制转换
     */
    public <T> T getExt(String key) {
        Jedis jedis = null;
        T obj = null;
        try {
            jedis = jedisPool.getResource();
            byte[] bytes = jedis.get(key.getBytes());
            if (bytes != null) {
                obj = ProtostuffUtils.deserializer(bytes, true);
            }
        } catch (JedisConnectionException e) {
            LOGGER.error("connect jedis failed.");
        } catch (Exception e) {
            LOGGER.error("get failed. key=" + key, e);
        } finally {
            try {
                Closeables.close(jedis, true);
            } catch (IOException e) {
                LOGGER.error("close jedis error when get key! key=" + key, e);
            }
        }
        return obj;
    }

	public void del(String... keys) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            if (jedis != null) {
            	jedis.del(keys);
            }
        } catch (JedisConnectionException e) {
            LOGGER.error("connect jedis failed.");
        } catch (Exception e) {
            LOGGER.error("getObject failed. key=" + keys, e);
        } finally {
            try {
                Closeables.close(jedis, true);
            } catch (IOException e) {
                LOGGER.error("close jedis error when get key! key=" + keys, e);
            }
        }
    }
	
	public long rpush(String key, Object... value){
		long result = 0;
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			if(jedis != null){
				byte[][] bytes = new byte[value.length][value.length];
				for(int i=0; i<value.length; i++){
					bytes[i] = ProtostuffUtils.serializer(value[i]);
				}
				result = jedis.rpush(key.getBytes(), bytes);
			}
		} catch (JedisConnectionException e) {
            LOGGER.error("connect jedis failed.");
        } catch (Exception e) {
            LOGGER.error("rpush failed. key=" + key, e);
        } finally {
            try {
                Closeables.close(jedis, true);
            } catch (IOException e) {
                LOGGER.error("close jedis error when get key! key=" + key, e);
            }
        }
		return result;
	}
	
	public Object lpop(String key, Class<?> clazz){
		Object result = null;
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			if(jedis != null){
				byte[] bytes = jedis.lpop(key.getBytes());
				result = ProtostuffUtils.deserializer(bytes, clazz);
			}
		} catch (JedisConnectionException e) {
            LOGGER.error("connect jedis failed.", e);
        } catch (Exception e) {
            LOGGER.error("rpush failed. key=" + key, e);
        } finally {
            try {
                Closeables.close(jedis, true);
            } catch (IOException e) {
                LOGGER.error("close jedis error when get key! key=" + key, e);
            }
        }
		return result;
	}
	
	public boolean publish(String channel, String message){
		boolean flag = false;
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			if(jedis != null){
				long result = jedis.publish(channel.getBytes(), ProtostuffUtils.serializer(message));
				flag = result == 1 ? true : false;
			}
		} catch (JedisConnectionException e) {
            LOGGER.error("connect jedis failed.", e);
        } catch (Exception e) {
            LOGGER.error("publish failed. channel=" + channel, e);
        } finally {
            try {
                Closeables.close(jedis, true);
            } catch (IOException e) {
                LOGGER.error("close jedis error when publish channel! channel=" + channel, e);
            }
        }
		return flag;
	}
	
	public void subscribe(JedisPubSub jedisPubSub, String... channels){
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			if(jedis != null){
				jedis.subscribe(jedisPubSub, channels);
			}
		} catch (JedisConnectionException e) {
            LOGGER.error("connect jedis failed.", e);
        } catch (Exception e) {
            LOGGER.error("subscribe failed.", e);
        } finally {
            try {
                Closeables.close(jedis, true);
            } catch (IOException e) {
                LOGGER.error("close jedis error when subscribe", e);
            }
        }
	}
	
	public Object eval(String script){
		Jedis jedis = null;
		Object result = null;
		try {
			jedis = jedisPool.getResource();
			if(jedis != null){
				result = jedis.eval(script);
			}
		} catch (JedisConnectionException e) {
            LOGGER.error("connect jedis failed.", e);
        } catch (Exception e) {
            LOGGER.error("eval failed.", e);
        } finally {
            try {
                Closeables.close(jedis, true);
            } catch (IOException e) {
                LOGGER.error("close jedis error when eval", e);
            }
        }
		return result;
	} 
	
	public Object eval(String script, List<String> keys, List<String> args){
		Jedis jedis = null;
		Object result = null;
		try {
			jedis = jedisPool.getResource();
			if(jedis != null){
				result = jedis.eval(script, keys, args);
			}
		} catch (JedisConnectionException e) {
            LOGGER.error("connect jedis failed.", e);
        } catch (Exception e) {
            LOGGER.error("eval failed. script: " + script, e);
        } finally {
            try {
                Closeables.close(jedis, true);
            } catch (IOException e) {
                LOGGER.error("close jedis error when eval", e);
            }
        }
		return result;
	} 
	
	/**
	 * codis半支持lua脚本的执行,需要使用hashtag来保证脚本中的key在同一个slot<br>
	 * Codis does not support cross-node operation, so you must use Hash Tags (See this blog's "Hash tags" section) to put all the keys 
	 * which may shown in one request into the same slot then you can use these commands. 
	 * Codis does not check if the keys have same tag, so if you don't use tag, your program will get wrong response.
	 * @param script lua脚本
	 * @param prefix hashtag的前缀，起标示作用，固定slot
	 * @param keys 键集合
	 * @param args 值集合
	 * @return
	 */
	public Object eval4Codis(String script, String prefix, List<String> keys, List<String> args){
		Jedis jedis = null;
		Object result = null;
		
		//当key为多个时，必须使用hashtag来保证所有key同属一个slot
		if(keys.size() > 1 && StringUtils.isBlank(prefix)){
			throw new IllegalArgumentException("prefix can not be null when keys size > 1!");
		}
		
		List<String> keys4Distlock = new ArrayList<String>();
		for(String key : keys){
			keys4Distlock.add("{ " + prefix + "}" + key);
		}
		try {
			jedis = jedisPool.getResource();
			if(jedis != null){
				result = jedis.eval(script, keys4Distlock, args);
			}
		} catch (JedisConnectionException e) {
            LOGGER.error("connect jedis failed.", e);
        } catch (Exception e) {
            LOGGER.error("eval failed. script: " + script, e);
        } finally {
            try {
                Closeables.close(jedis, true);
            } catch (IOException e) {
                LOGGER.error("close jedis error when eval", e);
            }
        }
		return result;
	} 
	
	/**
	 * 脚本中key只有一个时，可以不使用hashtag
	 */
	public Object eval4Codis(String script, String key, List<String> args){
		Jedis jedis = null;
		Object result = null;
		
		List<String> keys4Distlock = new ArrayList<String>();
		keys4Distlock.add(key);
		try {
			jedis = jedisPool.getResource();
			if(jedis != null){
				result = jedis.eval(script, keys4Distlock, args);
			}
		} catch (JedisConnectionException e) {
            LOGGER.error("connect jedis failed.", e);
        } catch (Exception e) {
            LOGGER.error("eval failed. script: " + script, e);
        } finally {
            try {
                Closeables.close(jedis, true);
            } catch (IOException e) {
                LOGGER.error("close jedis error when eval", e);
            }
        }
		return result;
	} 
	
	public void expire(String key, int seconds){
		Jedis jedis = null;
		try {
			jedis = jedisPool.getResource();
			if(jedis != null){
				jedis.expire(key, seconds);
			}
		} catch (JedisConnectionException e) {
            LOGGER.error("connect jedis failed.", e);
        } catch (Exception e) {
            LOGGER.error("expire failed. key: " + key, e);
        } finally {
            try {
                Closeables.close(jedis, true);
            } catch (IOException e) {
                LOGGER.error("close jedis error when expire", e);
            }
        }
	}
	
    public JedisResourcePool getJedisPool() {
        return this.jedisPool;
    }

	public void setJedisPool(JedisResourcePool jedisPool) {
		this.jedisPool = jedisPool;
	}
    
}
